/*
Colin Towle
Christopher Saunders

Graphics projectd
*/

#ifndef GAME
#define GAME

#include <cstdlib>
#include <iostream>
#include <math.h>
#include "Stereo/StereoView.h"
#include "collisions.h"
#include "Sprites/Scenegraph/parentnode.h"
#include "sprites.h"
#include "collisions/CollisionEngine.h"
#include "InvaderController.h"
#include "BackgroundLoader.h"

using std::cerr;
using std::endl;


float iniWidth = 7.5f;
float iniHeight = 7.5f;


// Window dimensions
struct WindowSize {
  int widthPixel;
  int heightPixel;
  float width;
  float height;
  float zNear;
  float zFar;

  WindowSize() :
    widthPixel(600), heightPixel(900),
    width(iniWidth), height(iniHeight),
    zNear(1.0f), zFar(40.0f)
  {} 
};

// Global variables
WindowSize g_winSize;  
sprites::Ship* ship;
game::InvaderController* invaders;
sprites::Barrier* barrier1;
sprites::Barrier* barrier2;
sprites::Barrier* barrier3;
sprites::Barrier* barrier4;
backgrounds::BackgroundLoader* backgroundLoader;

// Camera control variables
Vector3D shipLocation;
bool firstPerson = false;
bool rotateAliens = false;

// Stereo Viewing variables
bool usingStereo = false;
stereo::StereoView stereoWindow;
GLint stereoLeft, stereoRight;


collisions::CollisionEngine engine;

vector<sprites::Sprite*> spriteList;

int keyPressed = 0;
int g_startY = 0;
int g_startX = 0;
void myUpdateFunc();

/**
 * Moves the currently selected joint (right mouse button)
 */
void _moveJoint( int _x, int _y ) {
  g_startY = _y;
  myUpdateFunc();
}

/**
 * Mouse function callback - called on a mouse event
 */
void myMouseFunc( int _button, int _state, int _x, int _y ) {
/*
 * Mouse functions were not needed for our project, keeping this
 * here in case this functionality were ever needed / desired.
 */
}

/**
 * Display routine - draw content
 */
void myDisplay( void ) {
  // Clear the window
  glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  glMatrixMode(GL_MODELVIEW);
  
  glLoadIdentity();

  //draw the sprites
  for(unsigned int i = 0; i < spriteList.size(); i++)
  {
	  spriteList[i]->draw();
  }
  invaders->draw();
  glPopMatrix();

  //Are we using stereo viewing?  If so we turn off backgrounds to make
  //things easier to see.
  if(usingStereo){
	  //Set the background to black.
	  glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
  } else {
    //Otherwise, we enable our background texturing
	glPushMatrix();
	backgroundLoader = new backgrounds::BackgroundLoader(g_winSize.height,g_winSize.width,4.5f, firstPerson);
	glPopMatrix();
  }

  glPushMatrix();
  //We are going to need these values for us in case we are
  //running with stereo viewing.
  float zNear, vergence, separation;
  zNear = stereoWindow.zNear;
  vergence = stereoWindow.vergence;
  separation = stereoWindow.separation;
  GLint activeWindow = glutGetWindow();

  if(firstPerson){
	  shipLocation = ship->getPosition();
	  glMatrixMode(GL_PROJECTION); glLoadIdentity(); 
	  if(!usingStereo){
	  //We are running in single window mode
	  glFrustum(-g_winSize.width/2, g_winSize.width/2,
				-g_winSize.height/2, g_winSize.height/2, 
				g_winSize.zNear, g_winSize.zFar);
	  gluLookAt(shipLocation.X, shipLocation.Y+3.5, shipLocation.Z,
				shipLocation.X, 0, shipLocation.Z, 
				0.0, 0.0, 1.0);
	  } else {
		  //stereo vision mode
		  if(activeWindow == stereoLeft){
			  	  glFrustum(
					  (-(g_winSize.width+separation)/2),
					  ((g_winSize.width-separation)/2+separation),
							-g_winSize.height/2,
							g_winSize.height/2,
							g_winSize.zNear, g_winSize.zFar);
				gluLookAt(shipLocation.X, shipLocation.Y+2, shipLocation.Z,
						  shipLocation.X-vergence, 0, shipLocation.Z, 
						  0.0, 0.0, 1.0);
		  } else {
			  	 glFrustum(
							(-(g_winSize.width+separation)/2-separation),
							((g_winSize.width-separation)/2),
							-g_winSize.height/2,
							g_winSize.height/2,
							g_winSize.zNear, g_winSize.zFar);

				gluLookAt(shipLocation.X, shipLocation.Y+2, shipLocation.Z,
						  shipLocation.X+vergence, 0, shipLocation.Z, 
						  0.0, 0.0, 1.0);
		  }
	  }
	} else {
	glMatrixMode(GL_PROJECTION); glLoadIdentity();
	if(!usingStereo){
		//single window mode
		glFrustum( -g_winSize.width/2.0, g_winSize.width/2.0, 
			       -g_winSize.height/2.0, g_winSize.height/2.0,
				    g_winSize.zNear, g_winSize.zFar);
	} else {
		//stereo vision mode
		if(activeWindow == stereoLeft){
			glFrustum(
					  (-(g_winSize.width+separation)/2),
					  ((g_winSize.width-separation)/2+separation),
					  -g_winSize.height/2,
					   g_winSize.height/2,
					   g_winSize.zNear, g_winSize.zFar);
			gluLookAt(0, 0, 0.01f, -vergence, 0, -g_winSize.zFar, 0, 1, 0);
		} else {
			glFrustum(
					 (-(g_winSize.width+separation)/2-separation),
					 ((g_winSize.width-separation)/2),
					  -g_winSize.height/2,
					   g_winSize.height/2,
					   g_winSize.zNear, g_winSize.zFar);
			gluLookAt(0, 0, 0.01f, vergence, 0, -g_winSize.zFar, 0, 1, 0);
		}
	}
	}
  glPopMatrix();


  // flush -- here not really necessary
  glFlush();
  glutSwapBuffers();
}


/**
 * OpenGL reshape function - main window
 */
void myReshapeFunc( GLsizei _width, GLsizei _height ) 
{
  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  // adjust the view volume to the correct aspect ratio
  
    if ( _width / 600 > _height / 878 ) {
    g_winSize.width = 7.5f * ((GLfloat)_width/600)/((GLfloat)_height/878);
	g_winSize.height = 10.975f;
  } else {
	g_winSize.width = 7.5f;
    g_winSize.height = 10.975f * ((GLfloat)_height/878)/((GLfloat)_width/600);
  }

  /*if(g_winSize.width > 2 && g_winSize.height > 2)
  {
	  if(g_winSize.width > g_winSize.height)
	  {
		  g_winSize.width = g_winSize.width / g_winSize.height * 2;
		  g_winSize.height = 2;
	  }
	  else
	  {
		  g_winSize.height = g_winSize.height / g_winSize.width * 2;
		  g_winSize.width = 2;
	  }
  }*/

  /*if(_width / iniWidth <= _height / iniHeight)
  {
	  g_winSize.width = iniWidth;
	  g_winSize.height = iniHeight / ((_width / iniWidth) / (_height / iniHeight));
  }
  else
  {
	  g_winSize.height = iniHeight;
	  g_winSize.width = iniWidth / ((_height / iniHeight) / (_width / iniWidth));
  }*/


#if 1
	myDisplay();
 /* glFrustum( -g_winSize.width/2.0, g_winSize.width/2.0, 
	     -g_winSize.height/2.0, g_winSize.height/2.0, 
	     g_winSize.zNear, g_winSize.zFar );*/
#else
  glOrtho( -g_winSize.width/2.0, g_winSize.width/2.0, 
	   -g_winSize.height/2.0, g_winSize.height/2.0, 
	   g_winSize.zNear, g_winSize.zFar );
#endif
  g_winSize.widthPixel = _width;
  g_winSize.heightPixel = _height;
  // reshape our viewport
  glViewport( 0, 0, 
	      g_winSize.widthPixel,
	      g_winSize.heightPixel );
}


/**
 * Keyboard routine - handle keyboard input
 */
	void myKeyboardFunc(unsigned char _key, int _x, int _y ) 
	{
			switch( _key ) {
					// Toggle between geometric primitives
				case '+':
					stereoWindow.addSeparation(0.005f);
					break;
					// Zoom out
				case '-':
					stereoWindow.addSeparation(-0.005f);
					break;
					//switch to the next pivot node.
				case 'v':
					stereoWindow.moveVergence(-0.01f);
					break;
				case 'V':
					stereoWindow.moveVergence(0.01f);
					break;
				case 'n':
					break;
				//debug option - increase invader speed
				case 'c':
					firstPerson = !firstPerson;
					invaders->orientationFlag = true;
					break;
				case 'o':
					//extra background texture for our 3rd person view
					backgroundLoader->setTexture("resources/textures/9000.jpg", 0);
					break;
				case 'O':
					backgroundLoader->setTexture("resources/textures/cityscape2.jpg", 0);
					break;
				// q or ESC to quit
				case 'q':
				case 27:
					exit(0);
					break;
				case ' ':
					//slow the input down
					if(keyPressed >= 25)
					{
						keyPressed = 0;
						if(!ship->isShooting())
						spriteList.insert(spriteList.begin(), ship->shoot());
					}	
					break;
			}
			myUpdateFunc();
		return;
	}

/**
 * Special key routine
 */
void mySpecialKeyboardFunc( int _key , int _x, int _y ) {
  float offset[] = {0.0f, 0.0f, 0.0f};
  switch( _key ) 
    {
    case GLUT_KEY_LEFT:	
	  ship->moveLeft();
      break;
    case GLUT_KEY_RIGHT: 
	  ship->moveRight();
      break;
    default: 
      break;
    }
  myUpdateFunc();
  return;
}

/*
 * Verifies for the 'stereo' argument
 */
void verifyStereo(char* stereo){
	if(strcmp(stereo, "stereo") == 0) usingStereo = true;		
}

/*
 * Stereo Vision Initialization procedures
 */
void stereoDisplay( void ){
	glutSetWindow(stereoLeft);
	myDisplay();
	glutSetWindow(stereoRight);
	myDisplay();
	return;
}


/*
 * GLUT Initialization
 */
void myGlutInit(){
	glutMouseFunc( myMouseFunc );

	// Set up callback functions for key presses
	glutKeyboardFunc( myKeyboardFunc ); // Handles ascii symbols
	glutSpecialFunc( mySpecialKeyboardFunc ); // Handles function keys

	// Set the reshape callback
	glutReshapeFunc( myReshapeFunc );

	// do the drawing
	glutDisplayFunc( (usingStereo) ? stereoDisplay : myDisplay );

	// this draws the game
	glutIdleFunc(myUpdateFunc);
}

void initializeEnv(){
	if(usingStereo){
		//if we are using our stereo vision option, we create a new stereo vision object.
		stereoWindow = stereo::StereoView(g_winSize.zNear, g_winSize.zFar, g_winSize.widthPixel, g_winSize.heightPixel);
	}
  //initialize some basic objects
  ship = new sprites::Ship(Vector3D(0, -13, -5), collisions::SHIP, 0.8f, 0.3f);
  spriteList.push_back(ship);
  barrier1 = new sprites::Barrier(Vector3D(-10.0f, -8, -5), collisions::BARRIER, 3.0f);
  barrier2 = new sprites::Barrier(Vector3D(-3.33f, -8, -5), collisions::BARRIER, 3.0f);
  barrier3 = new sprites::Barrier(Vector3D( 3.33f, -8, -5), collisions::BARRIER, 3.0f);
  barrier4 = new sprites::Barrier(Vector3D( 10.0f, -8, -5), collisions::BARRIER, 3.0f);
  spriteList.push_back(barrier1);
  spriteList.push_back(barrier2);
  spriteList.push_back(barrier3);
  spriteList.push_back(barrier4);
}

/**
 * OpenGL initialization - set the state machine
 */
void myInit( bool isLeft ) {
  // dark background
  glClearColor( 0.2f, 0.2f, 0.2f, 0.0f );
  // color for drawing
  glColor3f( 1.0f, 0.4f, 0.1f );

  GLfloat light_ambient[] = { 0.2, 0.2, 0.2, 1.0 };
  GLfloat diffuse[]  = {1.0, 1.0, 1.0, 1.0 };
  GLfloat light_position[] = {-50.0, 50.0, 2.0, 1.0};

  glLightfv( GL_LIGHT0, GL_AMBIENT, light_ambient);
  glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuse);	
  glLightfv( GL_LIGHT0, GL_POSITION, light_position);

  glEnable( GL_LIGHTING );
  glEnable( GL_LIGHT0 );
  glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
  glEnable(GL_COLOR_MATERIAL);
   
  glShadeModel(GL_FLAT);

  // enable depth buffering
  glEnable(GL_DEPTH_TEST);

  // Set up viewing 3D world, perspective
  glMatrixMode(GL_PROJECTION);
  // set it to the identity matrix
  glLoadIdentity();
  if(usingStereo){
	  GLint activeWindow = glutGetWindow();
	  if(activeWindow == stereoLeft){
		  stereoWindow.setGLFrustum(0);
	  } else {
		  stereoWindow.setGLFrustum(1);
	  }
  } else {
  // use utility routine to set viewing volume
  glFrustum( -g_winSize.width/2.0, g_winSize.width/2.0, 
	     -g_winSize.height/2.0, g_winSize.height/2.0, 
	     g_winSize.zNear, g_winSize.zFar );
  }



  invaders = new game::InvaderController(3, 8);
  //for testing
  //invaders = new game::InvaderController(1, 1);


  spriteList = invaders->getInvaders();

  return;
}


void restartGame()
{
	invaders->reset();
	spriteList = invaders->getInvaders();

	//make sure the bullets are drawn first
	vector<sprites::Sprite*> temp = invaders->getBullets();
	for(unsigned int i = 0; i < temp.size(); i++)
	{
		//bullets must always be drawn first so we add them at the start of the list
		spriteList.insert(spriteList.begin(), temp[i]);
	}
	
	//reset objects
	ship->reset();
	barrier1->reset();
	barrier2->reset();
	barrier3->reset();
	barrier4->reset();

	//add them to the list
	spriteList.push_back(ship);
	spriteList.push_back(barrier1);
	spriteList.push_back(barrier2);
	spriteList.push_back(barrier3);
	spriteList.push_back(barrier4);

}

void myUpdateFunc()
{
	keyPressed++;
	//releases memory for things that must be destroyed.
	for(int i = 0; i < spriteList.size();)
	{
		if(spriteList[i]->isDead())
		{
			spriteList.erase(spriteList.begin()+i);
		}
		else
		{
			i++;
		}
	}

	if(invaders->hasReset())
	{
		//get the invaders from the controller
		vector<sprites::Sprite*> temp = invaders->getInvaders();

		//add them into the list again to be processed by the physics.
		for(unsigned int i = 0; i < temp.size(); i++)
		{
			spriteList.push_back(temp[i]);
		}
	}

	//make sure the bullets are drawn first
	vector<sprites::Sprite*> temp = invaders->getBullets();
	for(unsigned int i = 0; i < temp.size(); i++)
	{
		spriteList.insert(spriteList.begin(), temp[i]);
	}

	//check collisions
	engine.checkCollisions(spriteList);
    glutPostRedisplay();

	if(ship->isDead())
	{
		restartGame();
	}

    return;
}



using namespace scenegraph;
/**
 * Main for a simple GLUT application
 */
int main( int argc, char** argv ) {
  // Pass the program name on
	if(argc == 2) verifyStereo(argv[1]);
	//usingStereo = true;
	std::cout << "Stereo is enabled? " << (usingStereo ? "Yes" : "No") << std::endl;
  glutInit(&argc, argv);
  // Set the glut state
  // double buffering, RGB color and depth buffer 
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGB );
  glutInitWindowPosition( 0, 0 );
  stereoWindow.setWindowSize(0, g_winSize.widthPixel, g_winSize.heightPixel);
  glutInitWindowSize( g_winSize.widthPixel, g_winSize.heightPixel);

  //If we are using stereo, we will prepare our stereo views and do our rendering
  //completely differently (primarily, remove textures for things like the BG).
  if(usingStereo){
	  //Left Window
	  stereoLeft = glutCreateWindow("Right Eye - INVADERS ARE COMING!!!");
	  myInit(true);
	  initializeEnv();
	  myGlutInit();
	  //Right Window
	  stereoRight = glutCreateWindow("Left Eye - INVADERS ARE COMING!!!");
	  myInit(false);
	  initializeEnv();
	  myGlutInit();
	  glutMainLoop();
	  return 0;
  } else {
	  glutCreateWindow( "INVADERS ARE COMING!!!!" );
	  // Set OpenGL state
	  myInit(false);

	  initializeEnv();
	  myGlutInit();

	  // Go into forever loop and process events
	  glutMainLoop();
	  // never reached
	  return 0;	
  }
}


#endif
